#include "general/PhysicsManager.hpp"
#include "utils/Logger.hpp"
#include "objects/Cuboid.hpp"
#include "utils/Utils.hpp"
#include "utils/Timer.hpp"
#include "general/CellSpacePartition3D.hpp"

using namespace klasPhysics;

#pragma comment(lib, "Irrlicht.lib")
#include <time.h>
#include <iostream>
using namespace std;

/*
 * main.cpp
 *
 *  Created on: Jul 4, 2009
 *      Author: jake
 */

#include <irrlicht.h>

using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;

class MyEventReceiver : public IEventReceiver
{
public:
	// This is the one method that we have to implement
	virtual bool OnEvent(const SEvent& event)
	{
		// Remember whether each key is down or up
		if (event.EventType == irr::EET_KEY_INPUT_EVENT)
			KeyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;

		return false;
	}

	// This is used to check whether a key is being held down
	virtual bool IsKeyDown(EKEY_CODE keyCode) const
	{
		return KeyIsDown[keyCode];
	}

	MyEventReceiver()
	{
		for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
			KeyIsDown[i] = false;
	}

private:
	// We use this array to store the current state of each key
	bool KeyIsDown[KEY_KEY_CODES_COUNT];
};

const int WINDOW_WIDTH = 800, WINDOW_HEIGHT = 800, BPP = 32;
const bool ENABLE_FULLSCREEN = false, ENABLE_SHADOWS = true, ENABLE_VSYNC = false;

IrrlichtDevice *device = NULL;
IVideoDriver *driver = NULL;
ISceneManager *sMgr = NULL;
IGUIEnvironment *guiEnv = NULL;
MyEventReceiver *eventReceiver = NULL;

scene::ISceneNode *node;

bool createIrr()
{
	eventReceiver = new MyEventReceiver;

	// create out device
	device = createDevice(video::EDT_OPENGL, dimension2d<s32>(WINDOW_WIDTH, WINDOW_HEIGHT), BPP, ENABLE_FULLSCREEN, ENABLE_SHADOWS, ENABLE_VSYNC, eventReceiver);
	device->setWindowCaption(L"klasPhysics demo application");

	// if we failed, return false
	if(!device)
		return false;

	device->getCursorControl()->setVisible(false);

	driver = device->getVideoDriver();
	sMgr = device->getSceneManager();
	guiEnv = device->getGUIEnvironment();

	// if the driver, scene manager, or gui environment is null return false
	if(driver == NULL || sMgr == NULL || guiEnv == NULL || eventReceiver == NULL)
		return false;

	return true;
}

bool createMeshes()
{
	node = sMgr->addCubeSceneNode(50);
	if(node)
	{
		node->setPosition(core::vector3df(0, 0, 500));
		node->setMaterialTexture(0, driver->getTexture("../../media/wall.bmp"));
		node->setMaterialFlag(video::EMF_LIGHTING, false);
	}
	else
		return false;


	// create the camera
	SKeyMap keyMap[8];
	keyMap[0].Action = EKA_MOVE_FORWARD;
	keyMap[0].KeyCode = KEY_UP;
	keyMap[1].Action = EKA_MOVE_FORWARD;
	keyMap[1].KeyCode = KEY_KEY_W;

	keyMap[2].Action = EKA_MOVE_BACKWARD;
	keyMap[2].KeyCode = KEY_DOWN;
	keyMap[3].Action = EKA_MOVE_BACKWARD;
	keyMap[3].KeyCode = KEY_KEY_S;

	keyMap[4].Action = EKA_STRAFE_LEFT;
	keyMap[4].KeyCode = KEY_LEFT;
	keyMap[5].Action = EKA_STRAFE_LEFT;
	keyMap[5].KeyCode = KEY_KEY_A;

	keyMap[6].Action = EKA_STRAFE_RIGHT;
	keyMap[6].KeyCode = KEY_RIGHT;
	keyMap[7].Action = EKA_STRAFE_RIGHT;
	keyMap[7].KeyCode = KEY_KEY_D;

	sMgr->addCameraSceneNodeFPS(0, 100, 2, -1, keyMap, 8);
	sMgr->getActiveCamera()->setFarValue(500000);


	return true;
}


inline void Render()
{
	driver->beginScene(true, true, SColor(255, 100, 101, 140));
	sMgr->drawAll();
	guiEnv->drawAll();
	driver->endScene();
}




int main()
{

	// start up the engine
	createIrr();

	// add meshes
	createMeshes();


	// useful information!
	//guiEnv->addStaticText(L"Welcome to the klasPhysics tutorial application, with rendering provided by Irrlicht!", rect<s32>(10, 10, 260, 22), true);

	// -----------------------------------------------
	// start physics simulation
	Logger::Init("Log.txt");
	PhysicsManager manager;
	double gravForceFeet = -9.8;
	double FeetToKlasUnitRatio = 600 / 9.8;
	//manager.SetGravity(Vector3D(0.0, gravForceFeet*FeetToKlasUnitRatio, 0.0));
	Cuboid *cube = new Cuboid(Vector3D(0, 0, 500), Vector3D(1, 1, 1));
	manager.AddObject(cube);
	
	CellSpacePartition3D Partition(Vector3D(5000.0, 5000.0, 5000.0), Vector3D(10, 10, 10), 500);
	Partition.RenderCells(sMgr);
	

	iReal lastFPS = -1;

    // In order to do framerate independent movement, we have to know
    // how long it was since the last frame
    u32 then = device->getTimer()->getTime();


	float t = 0.0f;
	const float updateFPS = 100;
	const float dt = (1000.0f / updateFPS); // one update per frame @ 30 fps

	Timer updateTimer;
	float currentTime = updateTimer.GetElapsedMilliseconds();
	float accumulator = 0.0f;
	

	// main loop
	while(device->run())
	{
		const u32 now = device->getTimer()->getTime();
        const f32 frameDeltaTime = (f32)(now - then) / 1000.f; // Time in seconds
       // then = now;
		//accumulator += frameDeltaTime * 1000;

		if(eventReceiver->IsKeyDown(irr::KEY_ESCAPE))
			device->closeDevice();

		if(eventReceiver->IsKeyDown(irr::KEY_KEY_I))
			cube->AddForce(Vector3D(0, 1000, 0));
		if(eventReceiver->IsKeyDown(irr::KEY_KEY_K))
			cube->AddForce(Vector3D(0, -1000, 0));
		if(eventReceiver->IsKeyDown(irr::KEY_KEY_J))
			cube->AddForce(Vector3D(-1000, 0, 0));
		if(eventReceiver->IsKeyDown(irr::KEY_KEY_L))
			cube->AddForce(Vector3D(1000, 0, 0));
		if(eventReceiver->IsKeyDown(irr::KEY_KEY_U))
			cube->AddForce(Vector3D(0, 0, -1000));
		if(eventReceiver->IsKeyDown(irr::KEY_KEY_O))
			cube->AddForce(Vector3D(0, 0, 1000));

		
		accumulator += updateTimer.GetElapsedMilliseconds();
		updateTimer.Restart();
		//cout << accumulator << endl;

		while(accumulator >= dt)
		{
			manager.Update(t, dt / 1000.0);
			t += dt;
			accumulator -= dt;

			vector3df nodePosition;
			nodePosition.X = cube->GetLocation().x;
			nodePosition.Y = cube->GetLocation().y;
			nodePosition.Z = cube->GetLocation().z;

			node->setPosition(nodePosition);
		}


		Render();


		int fps = driver->getFPS();
        if (lastFPS != fps)
        {
            core::stringw tmp(L"klasPhysics [");
            tmp += driver->getName();
            tmp += L"] fps: ";
            tmp += fps;

            device->setWindowCaption(tmp.c_str());
            lastFPS = fps;
        }


	}

	device->drop();
	Logger::Shutdown();
	delete(cube);
	delete(eventReceiver);

	return 0;
}
